// $Id: GfxModule.C 6706 2008-01-27 07:09:02Z furtak $

// This is an ORTS file
// (c) Michael Buro
// (c) Tim Furtak
// (c) Keith Yerex
// (c) Sami Wagiaalla
// (c) Erik Benner
// licensed under the GPL

#include "GfxGlobal.H"
#include "Global.H"
#include "GameStateModule.H"
#include "Options.H"
#include "GameChanges.H"
#include "GameObj.H"
#include "GfxModule.H"
#include "Game.H"
#include "PlayerInfo.H"
#include "Octree.H"
#include "GfxObject.H"
#include "GfxTerrain.H"
#include "WaterTile.H"
#include "GfxAnimesh.H"
#include "Minimap.H"
#include "GameTile.H"
#include "Token.H"
#include "ScriptParser.H"
#include "Image.H"
#include "CWidget.H"
#include "TerrainModule.H"
#include "KeyMap.H"

#include "ScriptVector.H"
#include "ScriptObjSet.H"
#include "ScriptModel.H"
#include "ScriptEmitter.H"
#include "ProfileWidget.H"
#include "PlayerActions.H"

using namespace std;

#define OCTREE_CULLING 1

GfxModule *GUI_gfxm = 0; // fixme: shouldn't be global
// e.g. use functors to register/call script functions


const sint4 GfxModule::STYPE_GFXOBJ_ID = stype::next_id();
const sint4 GfxModule::STYPE_SOUND_ID = stype::next_id();
const sint4 GfxModule::STYPE_COLORTILESET_ID = stype::next_id();
const sint4 GfxModule::STYPE_COLRECT_ID = stype::next_id();   

//===================================================================


// fixme: get rid of constants
#define NZ          10 // max height
#define KEYB_OFF    75
#define ZOOM_OFF    0.1875
#define ROTATE_OFF  1
#define LROTATE_OFF 1


#define PARSE_ERR(s) {\
  errstr << endl << ": parse error at line " << tokenizer.cur_line();\
  ERR(s);\
}

#define PARSE_ERR2(s1, s2) {\
  errstr << endl << ": parse error at line " << tokenizer.cur_line();\
  ERR2(s1,s2);\
}

const char *GfxModule::APP_TITLE = "ORTS: OpenGL Client";

const sint4 WHEEL_UP   = 3;
const sint4 WHEEL_DOWN = 4;


//Events
const sint4 GfxModule::FROM         = EventFactory::new_who();
// now cwidgets can be addedstatic
//const sint4 GfxModule::ADD_CWIDGETS_MSG = EventFactory::new_what();
const sint4 GfxModule::DRAW_MSG         = EventFactory::new_what();
const sint4 GfxModule::SCRIPT_MSG       = EventFactory::new_what();


GfxModule *GfxGlutAdaptor::c = 0;

//===================================================================

/** Checks if (x,y) is inside a rectangle. */

static bool inside(sint4 x, sint4 y, const SDL_Rect &r) {
  return x >= r.x && y >= r.y && x < r.x + r.w && y < r.y + r.h;
}

//===================================================================

GLenum glerror(const char *where)
{
  int err=glGetError();
  switch(err)
  {
    case 0: break;
    case GL_INVALID_ENUM:
      errstr << where << ": GL invalid enum\n";
      break;
    case GL_INVALID_OPERATION:
      errstr << where << " : GL invalid operation\n";
      break;
    case GL_INVALID_VALUE:
      errstr << where << ": GL invalid value\n";
      break;
    default:
      errstr << where << ": GL error  #" << err <<endl;
      break;
  }
  return err;
}



//===================================================================

string GfxModule::collapse_file_path(const string &p)
{
  Vector<string> subpath;
  size_t i, j;
  i = 0;
  j = p.find(PATH_SEP);
  while (j != string::npos) {
    string s = p.substr(i, j-i);
    if (s == ".." && subpath.size() > 0) {
      subpath.pop_back();
    } else {
      subpath.push_back(s);
    }
    i = j + 1;
    j = p.find(PATH_SEP, j+1);
  }
  string bname = p.substr(i, p.length()-i);

  string cp;
  FORALL (subpath, i) { cp += *i + PATH_SEP; }
  cp += bname;

  return cp;
}

//===================================================================

GfxModule::GfxModule(PlayerActionHandler * phandler) : phandler(phandler)
{
	changes = 0;
	action_changes = 0;

	game    = 0;
	minimap = 0;
	terrain = 0;
	wireframe = 0;
	loading = false;
	quit    = false;
	gui_obj = 0;

  pathfinding_on = true;
  ScriptObjSet::gfxm = this;
  GfxObject::gfxm = this;

  if (GUI_gfxm) ERR("GUI_gfxm already set!");
  GUI_gfxm = this;
  GfxTerrainTile::add_type(0, GfxTerrainTile::UNKNOWN); // tile id 0 is for unknown tiles

  octree = 0; gfx_frames = 0;
  border = 8;
  mm_rect.x = mm_rect.y = mm_rect.w = mm_rect.h = 0;
  views_rect.x =  views_rect.y =  views_rect.w =  views_rect.h = 0;
  num_tiles = 0;
  
  frames = 0;
  slow_frames = 0;
  
  // Setup controls
  right_mouse_down  = false;
  middle_mouse_down = false;
  left_mouse_down   = false;
  pause_draw = false;
  last_mouse_x = last_mouse_y = 0;
  ctrl_down = shift_down = net_active = false;
  cur_click_state = NORMAL;
  
  MemberFunctionCall<GfxModule>::owner = this;
}

//===================================================================

GfxModule::~GfxModule()
{
  // cleanup

  if (gui_obj) gui_obj->destruct();

  {
    string path = sanitize_path(opt.path + PATH_SEP);
    string db_file = sanitize_path(opt.db_file);
    if (db_file != "") save_database(path + db_file);
  }

  if (octree)  { delete octree; octree = 0; }
  if (minimap) { delete minimap; minimap = 0; }
  // fixme: maybe more here: gfx objects ...
  
  if (!opt.no_sound) mixer.close();
}

//===================================================================

void GfxModule::init(Game &game_, const GameChanges &changes_,
                     const ActionChanges &action_changes_, const Options &opt_)
{
  game    = &game_;
  changes = &changes_;
  action_changes = &action_changes_;
  opt     = opt_;
  string path = sanitize_path(opt.path + PATH_SEP);
  
  // Init display
  init_GLUT_window();
  init_GL();

  GLint foo;
#define myget(i) glGetIntegerv(i, &foo); cout << #i " = " << foo << endl;
  myget(GL_MAX_TEXTURE_UNITS);
  myget(GL_MAX_TEXTURE_IMAGE_UNITS);
  myget(GL_MAX_TEXTURE_COORDS);
  myget(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS);
  myget(GL_MAX_VERTEX_UNIFORM_COMPONENTS);

  // Init splashscreen
  splashScreen = SplashScreen(opt.win_x, opt.win_y, opt.win_w, opt.win_h, version());
  splashScreen.set_max(90); // fixme: SERIOUSLY shouldn't be a constant
  if (opt.splash) {
    Image bg;
    if (bg.loadTGA(path + "splash.tga"))
      splashScreen.set_background(bg);
      
    splashScreen.draw();
  } else {
    splashScreen.turn_off();
  }

  // Note: Most of the original init code has been moved to the load method
  //       which now gets executed after the GLUT window has been created.
}

//-------------------------------------------------------------------

void GfxModule::load()
{
  Game::pick_game(game);

  splashScreen.set_current_task("Initializing random number generator");
  rand.srand(opt.seed);
  string path = sanitize_path(opt.path + PATH_SEP);  
  Model::clear_fast = opt.clear_fast;
  Model::verbose_load = opt.verbose_load;

  // Setup default view angles
  lx_angle = -23; ly_angle = 45;
  if (!game->is_rotated() && !opt.minimap_rot) ly_angle = 0;

  splashScreen++;
  
  splashScreen.set_current_task("Initializing Octree");
  dim = Coor3(game->get_map().get_width(), NZ, game->get_map().get_height());
  max_dim = max(dim.x, dim.z);
  octree = new Octree(Coor3(0,0,0), dim, 5); // was 6
#if !OCTREE_CULLING
  octree->culling_mode = Octree::NO_CULLING;
#endif
  splashScreen++;
  
  splashScreen.set_current_task("Allocating texture memory");
  if (glewInit() != GLEW_OK) ERR("GLEW init problem");

  initialize_colors();
  initialize_shadows();
  
  // Init blur effect
  if (opt.blur > 0) {

    // Clear accumulation buffer
    glClear(GL_ACCUM_BUFFER_BIT);
    blur_buffer = 0;

    // Allocate space for blur texture
    char *tmp = new char[opt.blur_res * opt.blur_res * 4];
    glGenTextures(1, &blur_tex);
    glBindTexture(GL_TEXTURE_2D, blur_tex);
    glEnable(GL_TEXTURE_2D);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, opt.blur_res, opt.blur_res, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmp);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    delete [] tmp;
  }
  splashScreen++;

  splashScreen.set_current_task("Initializing minimap and view layout");
  update_layout("1x1");
  minimap = new Minimap(game);

  GfxAnimesh::set_mode(opt.model_mode);
  if (opt.cheap_shadows) {
    Image image(path + "shadow.tga", 1.0);
    GLuint id = image.createTexture(Image::LINEAR);
    if (id) GfxAnimesh::set_default_shadow_tex(id);
  }

  GfxAnimesh::set_gamma(opt.model_gamma);
  GfxAnimesh::set_texmode(opt.texture_mode);
  GfxAnimesh::set_texlevel(opt.texture_level);
  splashScreen++;

  splashScreen.set_current_task("Initializing sound engine");
  if (!opt.no_sound) {
    if (mixer.open() < 0) REM("Couldn't open mixer!");
  }
  splashScreen++;
  
  splashScreen.set_current_task("Loading terrain");
  sint4 tx = game->get_map().get_width();
  sint4 ty = game->get_map().get_height();

  terrain = new GfxTerrain(*this, tx, ty, ground_tex, octree);
  GfxObject::terrain = terrain;
  {
    Image image(path + "water.tga", 1.0);
    WaterTile::tex = image.createTexture(Image::BILINEAR);
  }
  splashScreen++;

  splashScreen.set_current_task("Initializing script functions");
  add_script_functions(); // register GfxModule script functions
  splashScreen++;

  if (opt.db_file != "")
  {
    splashScreen.set_current_task("Loading database");
    load_database(sanitize_path(path + opt.db_file));
    splashScreen++;
  }

  if (opt.gui_file != "") {
    splashScreen.set_current_task("Loading interface");
    string gui_file = sanitize_path(opt.gui_file);
    cout << "LOADING INTERFACE" << endl;
    load_interface(path + gui_file);
    cout << endl;
    splashScreen++;
  }

  if (opt.tileset != "") {
    splashScreen.set_current_task("Loading tileset");
    string file = sanitize_path(opt.tileset);
    cout << "LOADING TILESET" << endl;
    load_tileset(path + file);
    cout << endl;
    splashScreen++;
  }

  splashScreen.set_current_task("Initializing script GUI");
  add_script_functions(); // register GfxModule script functions
  gui_obj = game->get_cplayer_info().gof.create("GUI");
  //gui_obj = game->get_cplayer().global_object_map["GUI"];
  if (gui_obj) {
    cout << "found global object GUI " << static_cast<void*>(gui_obj) << endl;

    ScriptObj *sob;
    ScriptVector *sv;

    sob = gui_obj->component("all");
    sv = dynamic_cast<ScriptVector*>(sob);
    if (sv == 0) ERR("'all' is not a ScriptVector");
    script_all_objs = &sv->data;

    sob = gui_obj->component("selected");
    sv = dynamic_cast<ScriptVector*>(sob);
    if (sv == 0) ERR("'selected' is not a ScriptVector");
    script_sel_objs = &sv->data;
  }

  // call_handlers(Event(FROM, ADD_CWIDGETS_MSG));

  ProfileWidget *pw = new ProfileWidget;
  add_cwidget(*pw);
  finish_cwidget(); // add any other pending cwidgets
  
  ObjAction *a = gui_obj ? gui_obj->action("event_resize") : 0;
  if (a) {
    SActParam p;
    p.obj.push_back(gui_obj);
    p.val.push_back(stype(opt.win_w));
    p.val.push_back(stype(opt.win_h));
    
    a->Act(p);
  }
  splashScreen++;
  splashScreen.set_current_task("Loading complete!");
  
  build_noise_textures();
  //cout << "FINISHED LOAD" << endl; 
}

//-------------------------------------------------------------------

// This method gets registered as the main GLUT display function 
// during loading.
void GfxModule::splash()
{    
  // If we haven't begun loading yet, let's start
  if (!loading) {
    loading = true;
    load();
    
    // Register the normal draw routine and callback events
    init_GLUT_events();
    
    splashScreen.turn_off();
  }
}


//===================================================================

void GfxModule::add_script_functions()
{
  //Sami--------------------------
  ScriptParser::add("psin",  0, 2,  GUI_psin);
  ScriptParser::add("pcos",  0, 2,  GUI_pcos);
  ScriptParser::add("atan2", 0, 2,  GUI_atan2);
  ScriptParser::add("set_cursor",    0,  1,  GUI_set_cursor);
  ScriptParser::add("ERR",           0,  -1, GUI_error);
  //END Sami----------------------

  ScriptParser::add("strcmp",        0,  2,  GUI_strcmp);
  ScriptParser::add("printf",        0, -1,  GUI_printf);
  ScriptParser::add("draw_text",     0,  3,  GUI_text);
  ScriptParser::add("draw_textf",    0,  4,  GUI_textf);
  ScriptParser::add("set_color",     0,  4,  GUI_Color);
  ScriptParser::add("draw_bitmap",   0,  5,  GUI_Bitmap);
  ScriptParser::add("draw_rect",     0,  4,  GUI_Rect);

  ScriptParser::add("draw_overlay",  1,  5,  &GfxModule::GUI_Overlay);
  ScriptParser::add("start_overlay", 1,  0,  &GfxModule::GUI_start_overlay);
  ScriptParser::add("end_overlay",   0,  0,  &GfxModule::GUI_end_overlay);
  ScriptParser::add("draw_model",    0,  4,  &GfxModule::GUI_DrawModel2D);

  ScriptParser::add("create_view",   0,  0,  &GfxModule::GUI_CreateView);
  ScriptParser::add("resize_view",   0,  5,  &GfxModule::GUI_ResizeView);
  ScriptParser::add("delete_view",   0,  1,  &GfxModule::GUI_DeleteView);

  ScriptParser::add("screen2world",  0,  3,  &GfxModule::GUI_Screen2World);
  ScriptParser::add("screen2obj",    0,  2,  &GfxModule::GUI_Screen2Obj);
  ScriptParser::add("deselect_obj",    0,  1,  &GfxModule::GUI_Deselect_obj);
  ScriptParser::add("minimap_pos",     0,  4,  &GfxModule::GUI_MinimapPos);

  ScriptParser::add("get_val",       0,  1,  &GfxModule::GUI_Get_DBVal);
  ScriptParser::add("set_val",       0,  2,  &GfxModule::GUI_Set_DBVal);
  ScriptParser::add("get_vec",       0,  2,  &GfxModule::GUI_Get_DBVec);
  ScriptParser::add("set_vec",       0,  2,  &GfxModule::GUI_Set_DBVec);

  ScriptParser::add("flatten_terrain", 1,  0, &GfxModule::GUI_FlattenTerrain);
  ScriptParser::add("unflatten_terrain", 1,  0, &GfxModule::GUI_UnFlattenTerrain);
  ScriptParser::add("show_grid",     0,  1,  GUI_Outline);
  ScriptParser::add("mark_tiles",    0,  4,  &GfxModule::GUI_MarkTiles);
  ScriptParser::add("unmark_tiles",  0,  1,  &GfxModule::GUI_UnmarkTiles);
  ScriptParser::add("mark_rect",     0,  7,  &GfxModule::GUI_MarkRect);
  ScriptParser::add("edit_rect",     0,  8,  &GfxModule::GUI_EditRect);
  ScriptParser::add("unmark_rect",   0,  1,  &GfxModule::GUI_UnmarkRect);

  ScriptParser::add("quit_game",     0,  0,  &GfxModule::GUI_quitGame);
  ScriptParser::add("toggle_pf",     0,  1,  &GfxModule::GUI_toggle_pathfinding);

  ScriptParser::add("load_bp",       0,  1,  &GfxModule::GUI_LoadBP);

  ScriptParser::add("load_sound",    0,  1,  &GfxModule::GUI_load_sound);
  ScriptParser::add("play_sound",    0, -1,  &GfxModule::GUI_play_sound);

  ScriptParser::add("broadcast",    -1, -1,  &GfxModule::GUI_broadcast);

  FORU (i, game->get_player_num()+1) { // +1 -> NPC
    PlayerInfo &pi = game->get_player_info(i);
    ScriptObjConstructor soc;

    soc = &ScriptObjSet::create;
    pi.gof.register_blueprint("ObjSet", soc, 0);

    soc = /*(ScriptObjConstructor)*/&ScriptModel::create;
    pi.gof.register_blueprint("Model", soc, 0);

    soc = &ScriptEmitter::create;
    pi.gof.register_blueprint("Emitter", soc, 0);
  }
}

//===================================================================

void GfxModule::play_sound(Sound &sound, real4 lvol, real4 rvol)
{
  if (opt.no_sound) return;
  mixer.play(sound,lvol,rvol);
}

//-------------------------------------------------------------------

Sound* GfxModule::load_sound(const std::string &filename)
{
  if (opt.no_sound) return 0;

  Sound *sound;

  FIND (name2sound, it, filename);
  if (it != name2sound.end()) {

    sound = it->second;

  } else {

    sound = new Sound;
    mixer.load_sound(filename, *sound);
    name2sound[filename] = sound;

  }
  return sound;
}

//===================================================================

/** Update the list of all GameObj ids. */

void GfxModule::update_id_list()
{
  script_all_objs->clear();

  FORALL (game_objs, i) {
    GameObj *obj = (*i)->gob;
    ScriptObj::check_ptr(obj);
    script_all_objs->push_back(stype(obj));
  }
}

//-------------------------------------------------------------------

/** Update the selected objects referred to by the interface scripts. */

void GfxModule::update_selected()
{
  script_sel_objs->clear();

  FORALL (sel_objs, i) {
    stype s((*i)->gob);
    script_sel_objs->push_back(s);
  }
  if (gui_obj) gui_obj->set_int("sel_change", 1, false);
}

//===================================================================

void GfxModule::add_GfxGameObject(GfxGameObject *ggo)
{
  //  if (*gob->sod.x < 0) return; // object is outside of the field
  assert(ggo);
  if (ggo->gob == 0) ERR("shouldn't be null");
  
  game_objs.insert(ggo);
  game2gfx[ggo->gob] = ggo;

  if (ggo->gob) script_all_objs->push_back(stype(ggo->gob));

  ggo->game_tick();
}

//-------------------------------------------------------------------

void GfxModule::del_GfxGameObject(GfxGameObject *ggo)
{
  game_objs.erase(ggo);
  if (sel_objs.find(ggo) != sel_objs.end()) {
    sel_objs.erase(ggo);
    update_selected();
  }

  GfxObject *gfx = ggo->gfx_obj;
  if (gfx) gfx->kill();

  delete ggo;
}

//-------------------------------------------------------------------

void GfxModule::add_GfxObject(GfxObject *go)
{
  assert(go);

  gfx_objs.insert(go);
  go->moved = true;
  octree->add(go);
}

//===================================================================

/** Apply view changes from the server as they relate to graphics objects. */

void GfxModule::process_changes()
{
  Game::pick_game(game);

#if 0
  cout << "new#=" << changes->new_objs.size()
       << " changed#= " << changes->changed_objs.size()
       << " vanished#= " << changes->vanished_objs.size()
       << " dead#= " << changes->dead_objs.size()
       << endl;
#endif

  
  // new tiles

  const real4 h_avg = 0.25/GameConst::HEIGHT_MULT;
  
  FORALL (changes->new_tile_indexes, i) {
    
    terrain->new_tile(*i, game);
    
    const GameTile &tile = game->get_map()(*i);
    sint4 h0, h1, h2, h3;
    Tile::Type tw, te;
    Tile::Split split;
    tile.get_topo(h0, h1, h2, h3, split);
    tile.get_type(tw, te);
    minimap->new_tile(game->get_map().ind2x(*i), game->get_map().ind2y(*i), 
                      static_cast<sint4>((h0+h1+h2+h3)*h_avg+0.5), tw);
  }

  // create new gfx objects

  FORALL (changes->new_objs, i) {

    GameObj *gob = (*i)->get_GameObj();
    if (gob == 0) continue;

    if (*gob->sod.shape == Object::SHAPE_UNDEF) continue; // don't know how to draw the object
    GfxGameObject *ggo = new GfxGameObject(gob);
    add_GfxGameObject(ggo);
    
    ObjAction *init = gob->action("GfxInit");
    if (init) {
      SActParam p;
      p.obj.push_back(gob);
      init->Act(p);
    } else {
      cout << gob->bp_name() << " object has no GfxInit function" << endl;
    }
  }
  
  // update changed gfx objects

  FORALL (changes->changed_objs, i) {
    GameObj *gob = (*i)->get_GameObj();
    if (gob == 0) continue;

    FIND (game2gfx, j, gob);
    if (j == game2gfx.end()) continue;

    // update object in octree iff position or size has changed
    
    if (gob->attr_changed("x") ||
        gob->attr_changed("y") ||
        gob->attr_changed("heading") ||
        gob->attr_changed("last_direction") ||
        gob->attr_changed("radius") ||
        gob->attr_changed("width") ||
        gob->attr_changed("height") ||
        gob->attr_changed("zcat")) {
      
      GfxGameObject *p = (*j).second;
      GfxObject *gfx = p->gfx_obj;
      if (gfx) octree->remove(gfx);
      
      p->game_tick();

      sintptr *xptr = gob->get_int_ptr("x");
      if (!xptr || *xptr < 0) continue;
      
      if (gfx) octree->add(gfx);
    }
  }
  
  // remove vanished gfx objects
  
  FORALL (changes->vanished_objs, i) {
    GameObj *gob = (*i)->get_GameObj();
    if (gob == 0) continue;

    FIND (game2gfx, j, gob);
    if (j == game2gfx.end()) continue;
    
    GfxGameObject *ggo = j->second;
    bool is_static = false;
    if (ggo->gob) {
      sintptr *tmp = ggo->gob->get_int_ptr("static");
      if (tmp) is_static = *tmp;
    }
    
    if (is_static) { // implies that there is a gameobj

      static_objects.push_back(ggo);
      game_objs.erase(ggo);
      ggo->gob->add_ref();

    } else {

      gob->kill();
      game2gfx.erase(j);
      del_GfxGameObject(ggo);

    }

    ScriptObjSet::invalidate_obj(*i); // update obj lists
  }

  // remove dead GfxGameObjects
  
  FORALL (changes->dead_objs, i) {
    GameObj *gob = (*i)->get_GameObj();
    if (gob == 0) continue;

    gob->kill();
    FIND (game2gfx, j, gob);
    if (j == game2gfx.end()) continue;
    GfxGameObject *ggo = j->second;

    game2gfx.erase(j);
    del_GfxGameObject(ggo);
    
    ScriptObjSet::invalidate_obj(*i); // update obj lists
  }

  if (changes->vanished_objs.size() +
      changes->dead_objs.size() > 0)
    update_id_list();

  {
    sint4 watch;
    ::Options::get("-watch", watch);
    if (watch > 0) {
      FORU (i, game->get_player_num()) {
        if (watch & (1 << i)) game->compute_view(i);
      }
    } else {
      //      game->compute_view(game->get_client_player());
      game->compute_views(game->get_client_player());
    }
  }

  check_static_objects();
}

//===================================================================

/** Look at all of the static objects and see if they should be visible.
    Does not care if the object is still there or not.  If it still exists
    it will have been sent again.
*/

void GfxModule::check_static_objects()
{
  sint4 tile_points = game->get_tile_points();

  const Map<GameTile> &pf = game->get_map();
  PlayerInfo &pi = game->get_cplayer_info();
  
  uint4 size = static_objects.size();
  FORU_DYN (s, size) {

    bool visible = false;

    GfxGameObject *ggo = static_objects[s];
    GameObj *gob = ggo->gob;

    if (*gob->sod.x < 0 ||
        *gob->sod.y < 0 ||
        *gob->sod.x / tile_points >= pf.get_width() ||
        *gob->sod.y / tile_points >= pf.get_height()) continue;

    //    if (*gob->sod.cloaked2 >= 1) continue;
    
    MapView *mapW;
    MapView *mapE;
    
    if (*gob->sod.cloaked >= 1) {
      mapW = &pi.detected_W; mapE = &pi.detected_E;
    } else {
      mapW = &pi.visible_W;  mapE = &pi.visible_E;
    }
    
    Vector<sint4> obj_tiles_W;
    Vector<sint4> obj_tiles_E;
    
    game->compute_footprint(*gob, obj_tiles_W, obj_tiles_E);
    
    FORALL (obj_tiles_W, i) {
      sint4 x = pf.ind2x(*i);
      sint4 y = pf.ind2y(*i);
      
      if (mapW->is_set(x, y)) {
        visible = true;
        break;
      }
    }

    if (!visible)
      FORALL (obj_tiles_E, i) {
	sint4 x = pf.ind2x(*i);
	sint4 y = pf.ind2y(*i);
      
	if (mapE->is_set(x, y)) {
	  visible = true;
	  break;
	}
      }

    // destroy the old gfxobject
    if (visible) {

      gob->kill();

      FIND (game2gfx, j, gob);
      assert (j != game2gfx.end());

      GfxGameObject *ggo = j->second;
      del_GfxGameObject(ggo);
      game2gfx.erase(j);

      gob->del_ref(); // this should delete the GameObj
      static_objects[s] = static_objects[size-1];
      --size;
      --s;
    }

  }
  static_objects.resize(size);
}

//===================================================================

void GfxModule::start_loop(LoopFunctor *l) {
  looper = l;
  glutMainLoop();
}

//===================================================================

// see looper definition in ortsg_main.C

void GfxModule::loop() 
{
  looper->loop();
}

//===================================================================

// GfxObj* + int

struct GameObjInt {
  GameObj *gob;
  sint4 dist;
};

struct GameObjInt_Cmp {

  bool operator()(const GameObjInt &p1, const GameObjInt &p2) { return p1.dist < p2.dist; }

};

REGISTER_TYPEOF(222, Vector<GameObjInt>::iterator);

//===================================================================

/// send selected objects to (locx, locy) [real] in formation

void GfxModule::move_objects(real4 locx, real4 locy)
{
  Game::pick_game(game);

  // convert to fine grid
  locx *= game->get_tile_points(); 
  locy *= game->get_tile_points();  
  
  // compute closest object

  Vector<GameObjInt> vv;
  sint4 sx=0, sy=0;
  
  sint4 min_speed = 100000;

  FORALL (sel_objs, i) {
    GameObjInt v;
    v.gob = (*i)->gob;
    sint4 ox = *(*i)->gob->sod.x;
    sint4 oy = *(*i)->gob->sod.y;
    sx += ox;
    sy += oy;
    v.dist = sint4(sqrt(square(double(locx-ox)) + square(double(locy-oy))));
    vv.push_back(v);

    sint4 speed = *(*i)->gob->sod.max_speed;
    if (speed && speed < min_speed)
      min_speed = speed;
  }
  if (min_speed == 100000) min_speed = 0;

  sort(vv.begin(), vv.end(), GameObjInt_Cmp());

  enum { FORMATION, LOC, ANTI_LOC } mode;

  mode = FORMATION;
  
  if (shift_down) mode = LOC;
  if (ctrl_down)  mode = ANTI_LOC;

  // center of gravity
  sint4 loc0x = sx/sel_objs.size();
  sint4 loc0y = sy/sel_objs.size();

  {
    sint4 me = game->get_client_player();
    real4 x, y;
    
    FORALL (vv, i) {
      GameObj *gob = (*i).gob;

      if (*gob->sod.owner == me) {

        switch (mode) {
        case FORMATION:
          x = locx + *gob->sod.x - loc0x;
          y = locy + *gob->sod.y - loc0y;
          break;
        case LOC:
          x = locx;
          y = locy;
          break;
        case ANTI_LOC:
#if 1
          x = 2**gob->sod.x - locx;
          y = 2**gob->sod.y - locy;
#else
          if (abs(*gob->sod.x - locx) > abs(*gob->sod.y - locy)) {
            locy = *gob->sod.y;
          } else {
            locx = *gob->sod.x;
          }
          x = locx;
          y = locy;
#endif
          break;
        default: ERR("whoops");
        }

        if (x < 0) x = 0;
        if (y < 0) y = 0;
        //        cout << "x,y = " << x << " " << y << endl;

        if (pathfinding_on)
		{
			if(phandler)
			{
				phandler->move(gob, my_round(x), my_round(y));
			}
			else
			{
				TerrainBase::Loc target = TerrainBase::Loc(my_round(x),
														   my_round(y));
				call_handlers(TerrainModule::PathEvent(FROM,
					TerrainModule::FIND_PATH_MSG,
				    TerrainBase::Obj2LocTask(gob, target)));
			}
        } else {
          Vector<sint4> params;
          params.push_back(my_round(x));
          params.push_back(my_round(y));
          params.push_back(min_speed);
          gob->set_action("move", params);
        }
        cout << "SENT obj to: " << x << " " << y << endl;
      }
    }
  }
}

//===================================================================

void GfxModule::update_active_view()
{
  sint4 x = last_mouse_x;
  sint4 y = last_mouse_y;

  FORS_REV (i, views.size()) {
    if (views[i].inside(x, y)) {
      active_view = i;
      return;
    }
  }

  // not found: clip if necessary, otherwise leave as is
  if (active_view >= views.size()) active_view = 0;
}

//-------------------------------------------------------------------

void GfxModule::load_database(const string &db_name)
{
  ifstream file;
  file.open(db_name.c_str(), ios::binary);
  if (!file.is_open()) {
    cerr << "can't open file: " << db_name << endl;
    return;
  }

  // read file into buffer
  sint4 length;
  char *buffer;

  file.seekg(0, ios::end);
  length = file.tellg();
  file.seekg(0, ios::beg);

  buffer = new char[length+1];
  file.read(buffer, length);
  file.close();
  buffer[length] = 0;

  string key, value;

  Tokenizer tokenizer;
  tokenizer.init(buffer);
  tokenizer.set_line(0);

  while (tokenizer.good()) {
    key = tokenizer.next_token();
    if (key == "") break;
    if (tokenizer.token_type() != Tokenizer::STRING) {
      PARSE_ERR("key expected");
    }
    key = key.substr(1, key.size()-2);

    value = tokenizer.next_token();
    if (tokenizer.token_type() != Tokenizer::STRING) {
      PARSE_ERR("value expected");
    }
    value = value.substr(1, value.size()-2);

    script_db[key] = value;
  }

  delete [] buffer;
}

//-------------------------------------------------------------------

void GfxModule::save_database(const string &db_name)
{
  ofstream file(db_name.c_str());
  if (!file.is_open()) ERR2("can't open file: ", db_name);
  
  string key, value;

  FORALL (script_db, it) {
    file << "\"" << (*it).first << "\"\t\"" << (*it).second << "\"" << endl;
  }

  file.close();
}

//===================================================================

void GfxModule::load_tileset(const string &filename)
{
  ifstream file;
    
  file.open(filename.c_str(), ios::binary);
  if (!file.is_open()) {
    cerr << "can't open file: " << filename << endl;
    return;
  }

  // read file into buffer
  sint4 length;
  char *buffer;

  file.seekg(0, ios::end);
  length = file.tellg();
  file.seekg(0, ios::beg);

  buffer = new char[length+1];
  file.read(buffer, length);
  file.close();
  buffer[length] = 0;

  string token;

  Tokenizer tokenizer;
  tokenizer.init(buffer);
  tokenizer.set_line(0);

  sint4 tile_id = 1;

  // for now just read in the tile type - not used
  while (tokenizer.good()) {
    token = tokenizer.next_token();
    if (token == "") break;
    sint4 type = atoi(token.c_str());
    GfxTerrainTile::GeometryType gt = static_cast<GfxTerrainTile::GeometryType>(type);
    GfxTerrainTile::add_type(tile_id, gt);
    ++tile_id;
  }

  delete [] buffer;
}

//Sami=========================================================================
void GfxModule::add_cwidget(CWidget &widget)
{
  Game::pick_game(game);

  ObjAction *c = gui_obj ? gui_obj->action("add_cwidget") : 0;
  if (c) {
    SActParam p;
    p.obj.push_back(gui_obj);
    p.obj.push_back(&widget);

    p.val.push_back(stype(widget.get_layout()));
    p.val.push_back(stype(widget.get_name())); 
    
    stype result = c->Act(p);
    if (!!result) cout << "CWidget added " << endl;
  } else {
    cwidget_queue.push_back(&widget);
    //    cout << "Cannot add CWidget yet -- " << widget.get_name() << endl;
  }
}

//-----------------------------------------------------------------------------

void GfxModule::finish_cwidget()
{
  FORALL (cwidget_queue, it) {
    add_cwidget(**it);
  }
  cwidget_queue.clear();
}

//Sami=========================================================================
// note : the script cwidget destructor does not call the c destructor so 
// a cwidget still has to be deleted
void GfxModule::rem_cwidget(CWidget &widget)
{
  Game::pick_game(game);

  ObjAction *c = gui_obj ? gui_obj->action("rem_cwidget") : 0;
  if (c) {
    SActParam p;
    p.obj.push_back(gui_obj);
    p.obj.push_back(&widget);
    
    stype result = c->Act(p);
    if (!!result) cout << "CWidget removed " << endl;
  }
}


//=============================================================================
// GLUT event handlers
//=============================================================================

void GfxModule::event_resize(int width, int height)
{
  Game::pick_game(game);

  opt.win_w = width;
  opt.win_h = height;
  
  if(splashScreen.enabled()) {
    splashScreen.set_dim(0, 0, width, height);
    //cout << endl << "resize" << endl << endl;
    return;
  }

  //  glutReshapeWindow(width, height);

  views_rect.x = views_rect.y = 0;
  views_rect.w = opt.win_w;
  views_rect.h = opt.win_h;
  
  ObjAction *c = gui_obj ? gui_obj->action("event_resize") : 0;
  if (c) {
    SActParam p;
    p.obj.push_back(gui_obj);
    p.val.push_back(stype(opt.win_w));
    p.val.push_back(stype(opt.win_h));
    
    stype result = c->Act(p);
    if (!!result) return;

  } else {
    update_layout(prev_layout_name, true);
  }
}


//===================================================================

void GfxModule::event_key_down(unsigned char key, int mx, int my)
{
  Game::pick_game(game);

  my = opt.win_h-1-my;
  key = KeyMap::glut_key(key);

  int mod = glutGetModifiers();
  set_mods();

  ObjAction *c = gui_obj ? gui_obj->action("event_key_down") : 0;
  if (c) {
    SActParam p;
    p.obj.push_back(gui_obj);
    p.val.push_back(stype(key));
    p.val.push_back(stype(mod));
    p.val.push_back(stype(mx));
    p.val.push_back(stype(my));

    stype result = c->Act(p);
    if (!!result) return;
  }

  switch (key) {
  case '=':
    ShaderProgram::reload_all();
    break;
  case ']':
    views[active_view].zoom(-ZOOM_OFF); views[active_view].write();
    break;
  case '[':
    views[active_view].zoom(ZOOM_OFF); views[active_view].write();
    break;
  case ',':
    views[active_view].rotate_y(-ROTATE_OFF); views[active_view].write();
    break;
  case '.':
    views[active_view].rotate_y(ROTATE_OFF); views[active_view].write();
    break;
  case 'z':
    views[active_view].rotate_x(-ROTATE_OFF); views[active_view].write();
    break;
  case 'x':
    views[active_view].rotate_x(ROTATE_OFF); views[active_view].write();
    break;
  case 'q':
    GfxView::change_angle(lx_angle, -LROTATE_OFF);
    cout << "lxa=" << lx_angle << " lya=" << ly_angle << endl;
    break;
  case 'w':
    GfxView::change_angle(lx_angle, LROTATE_OFF);
    cout << "lxa=" << lx_angle << " lza=" << ly_angle << endl;    
    break;
  case 'e':
    GfxView::change_angle(ly_angle, -LROTATE_OFF);
    cout << "lxa=" << lx_angle << " lza=" << ly_angle << endl;    
    break;
  case 'r':
    GfxView::change_angle(ly_angle, LROTATE_OFF);
    cout << "lxa=" << lx_angle << " lza=" << ly_angle << endl;    
    break;
    //  case '1': update_layout("1x1");  break;
    //  case '2': update_layout("1x2");  break;
    //  case '3': update_layout("2t1b"); break;
    //  case '4': update_layout("2x2");  break;

  case 'p': opt.perspective_mode = !opt.perspective_mode; break;
  case 'f': wireframe = !wireframe; break;
//  case ' ': saveterrain();break;

//  case 27:
//    quit = true;
//    break;

  default:
    break;
  }
}

void GfxModule::event_special_key_down(int key, int mx, int my)
{
  Game::pick_game(game);

  my = opt.win_h-1-my;
  key = KeyMap::glut_special_key(key);

  int mod = glutGetModifiers();
  set_mods();

  ObjAction *c = gui_obj ? gui_obj->action("event_key_down") : 0;
  if (c) {
    SActParam p;
    p.obj.push_back(gui_obj);
    p.val.push_back(stype(key));
    p.val.push_back(stype(mod));
    p.val.push_back(stype(mx));
    p.val.push_back(stype(my));

    stype result = c->Act(p);
    if (!!result) return;
  }
  
  switch (key) {
    // no CTRL/SHIFT down events in glut
    //case GLUT_KEY_LCTRL:
    //    ctrl_down = true;
    //    break;
    //  case GLUT_KEY_LSHIFT:
    //    shift_down = true;
    //    break;
  case KeyMap::KEY_LEFT:
    views[active_view].move_x(-KEYB_OFF * max_dim * 0.75); views[active_view].write();
    break;
  case KeyMap::KEY_RIGHT:
    views[active_view].move_x(KEYB_OFF * max_dim * 0.75); views[active_view].write();
    break;
  case KeyMap::KEY_UP:
    views[active_view].move_y(KEYB_OFF * max_dim * 0.75); views[active_view].write();
    break;
  case KeyMap::KEY_DOWN:
    views[active_view].move_y(-KEYB_OFF * max_dim * 0.75); views[active_view].write();
    break;
  case KeyMap::KEY_PAGEUP:
    views[active_view].y_off -= 0.05;
    break;
  case KeyMap::KEY_PAGEDOWN:
    views[active_view].y_off += 0.05;
    break;
  case KeyMap::KEY_F11:
    pause_draw = !pause_draw;
    break;
  default:
    break;
  }
}


//===================================================================

void GfxModule::event_key_up(unsigned char key, int mx, int my)
{
  Game::pick_game(game);

  my = opt.win_h-1-my;
  key = KeyMap::glut_key(key);

  int mod = glutGetModifiers();
  set_mods();
  ObjAction *c = gui_obj ? gui_obj->action("event_key_up") : 0;

  if (c) {
    SActParam p;
    p.obj.push_back(gui_obj);
    p.val.push_back(stype(key));
    p.val.push_back(stype(mod));
    p.val.push_back(stype(mx));
    p.val.push_back(stype(my));
    
    stype result = c->Act(p);
    if (!!result) return;
  }
}

//===================================================================

void GfxModule::event_mouse_up(int button, int /*state*/, int mx, int my)
{
  Game::pick_game(game);

  my = opt.win_h-1-my;

  set_mods();
  
  if (button == GLUT_LEFT_BUTTON)   { left_mouse_down   = false; }
  if (button == GLUT_MIDDLE_BUTTON) { middle_mouse_down = false; }
  if (button == GLUT_RIGHT_BUTTON)  { right_mouse_down  = false; }
 
  // call the GUI mouse_up event handler
  // Sending the event to the GUI before the rest of this function shouldn't cause any side-effects,
  // the only actions that should care enough to block the event should have already trapped mouse_down.
  ObjAction *c = gui_obj ? gui_obj->action("event_mouse_up") : 0;
  if (c) {
    SActParam p;
    p.obj.push_back(gui_obj);
    p.val.push_back(stype(mx));
    p.val.push_back(stype(my));
    p.val.push_back(stype(button));
    
    stype result = c->Act(p);
    if (!!result) return;
  }

  // ignore over minimap
  if (inside(last_mouse_x, last_mouse_y, mm_rect) && !net_active) return;
  
  if (button == GLUT_LEFT_BUTTON ) {

    if (cur_click_state == JUST_CLICKED) {
      cur_click_state = NORMAL;
      return;
    }
    if (cur_click_state != NORMAL) return;

	// Creates rect or "rubber-band" from which a player's units will
	// be selected
    Vec3<real4> inter;
    Vector<IdZ> ids;
    SDL_Rect r;
    sint4 dx = abs(sint4(last_mouse_x) - sint4(button1_down_x));
    sint4 dy = abs(sint4(last_mouse_y) - sint4(button1_down_y));
    bool net_select = net_active && (dx > 0 && dy > 0 && (dx >= 5 || dy >= 5));

    if (net_select) {

      compute_rect(button1_down_x, button1_down_y, last_mouse_x, last_mouse_y, r);

    } else {

      if (!inside(last_mouse_x, last_mouse_y, views[active_view].viewport)) {
        net_active = false;
        return;
      }

      sint4 w = 4; // > 0

      SDL_Rect r0 = {  last_mouse_x - w/2,  last_mouse_y - w/2, w, w };
      r = r0;

    }

    net_active = false;

    rect_intersection(views[active_view].viewport, r, r);
    
    pick(views[active_view], r, inter, ids);

    if (!ids.empty()) {

      //      cout << ids.size() << " hit(s)" << endl;
      
	  // TDH1 -
	  // This lets "Like-selection" act as anticipated
	  if (alt_down){
		return;
	  }
      else if (!shift_down) {

        // new selection if shift key not pressed
        FORALL (sel_objs, i) (*i)->gfx_obj->selected = false;
        sel_objs.clear();
      }
      
      FORALL (ids, i) {

        GfxObject *go = GfxObject::id2obj((*i).id >> GfxObject::SUB_ID_LOG);
        if (go == 0) { REM("foo"); continue; }
        if (dynamic_cast<GfxTerrainTile*>(go)) continue;

        GfxGameObject *ggo = GfxGameObject::get_gfx(go);
        if (!ggo) continue;
        
        FIND (sel_objs, j, ggo);
        if (net_select || j == sel_objs.end()) {
          
          // add object to selection
          
          sel_objs.insert(ggo);
          if (ggo->gfx_obj) ggo->gfx_obj->selected = true;
          
        } else {
          // remove object from selection
          if (ggo->gfx_obj) ggo->gfx_obj->selected = false;
          sel_objs.erase(j);
        }
      }
      
      update_selected();

      if (sel_objs.size() > 1) {

        Vector<GfxGameObject*> del;

		// TDH1 - Fixed multiple unit selection.
		
        sint4 me = game->get_client_player();
        FORALL (sel_objs, i){
        	// Remove other players' objects and scenery
			if (*(*i)->gob->sod.owner != me) del.push_back(*i);
			// Remove buildings, objects that don't move
			else if (*(*i)->gob->sod.max_speed == 0 )del.push_back(*i);
			// Remove Terran flight capable building, this is a hack but no better method is available currently
			else if (*(*i)->gob->sod.shape != Object::CIRCLE) del.push_back(*i);
		}
        FORALL (del, i) { (*i)->gfx_obj->selected = false; sel_objs.erase(*i); }
        update_selected();
      }
    } else if (!net_select) {
      FORALL (sel_objs, i) (*i)->gfx_obj->selected = false;
      sel_objs.clear();
      update_selected();
    }
  }

  update_active_view();
  last_mouse_x = mx;
  last_mouse_y = my;
}

//===================================================================

void GfxModule::event_mouse_down(int button, int state, int mx, int my)
{
  Game::pick_game(game);

  if (state == GLUT_UP ) {
    event_mouse_up(button,state,mx,my);
    return;
  }
  my = opt.win_h-1-my;
  set_mods();

  if (button == GLUT_LEFT_BUTTON)   { left_mouse_down   = true; }
  if (button == GLUT_MIDDLE_BUTTON) { middle_mouse_down = true; }
  if (button == GLUT_RIGHT_BUTTON)  { right_mouse_down  = true; }

  // call the GUI mouse_down event handler
  ObjAction *c = gui_obj ? gui_obj->action("event_mouse_down") : 0;
  if (c) {
    SActParam p;
    p.obj.push_back(gui_obj);
    p.val.push_back(stype(mx));
    p.val.push_back(stype(my));
    p.val.push_back(stype(button));
    
    stype result = c->Act(p);
    
    if (!!result) {
      cur_click_state = JUST_CLICKED;
      return;
    }
  }
  
  if (button == GLUT_LEFT_BUTTON) {
    button1_down_x = last_mouse_x;
    button1_down_y = last_mouse_y;
    net_active = false;


	// TDH1 - "Select like units" option
	/////////////////////////////////
	// Holding Alt and clicking on unit will select like units
	if (alt_down && !inside(last_mouse_x, last_mouse_y, mm_rect)) {

		std::set<GfxGameObject*> clicked_objs; // selected objs
		
		/////////////////////////////////
		// Get the unit that was clicked
		Vec3<real4> clickedinter;
		Vector<IdZ> clickedids;
		SDL_Rect clickedObjRect;

	    sint4 w = 4; // > 0
	    SDL_Rect r0 = { button1_down_x - w/2, button1_down_y - w/2, w, w };
	    clickedObjRect = r0;
	
	    rect_intersection(views[active_view].viewport, clickedObjRect, clickedObjRect);
	    pick(views[active_view], clickedObjRect, clickedinter, clickedids);	
		update_selected();
		
		/////////////////////////////////
		// Select all units near the unit clicked
		Vec3<real4> inter;
		Vector<IdZ> ids;
		SDL_Rect r;

		// TDH1 - TODO - Time willing I'd like to tune this up a bit...
		// At the furthest zoomed out level the "select like" option doens't work
		
		compute_rect(button1_down_x - views[active_view].viewport.w/2, button1_down_y - views[active_view].viewport.h/2, button1_down_x + views[active_view].viewport.w/2, button1_down_y + views[active_view].viewport.h/2, r);
		rect_intersection(views[active_view].viewport, r, r);
		pick(views[active_view], r, inter, ids);

		/////////////////////////////////
		// If something was clicked 
		if (!ids.empty()) {

		  //This should only be one unit, the one clicked
		  FORALL (clickedids, i) {
		  
		    GfxObject *go = GfxObject::id2obj((*i).id >> GfxObject::SUB_ID_LOG);
		    if (go == 0) { REM("foo"); continue; }
		    if (dynamic_cast<GfxTerrainTile*>(go)) continue;
		  
		    GfxGameObject *ggo = GfxGameObject::get_gfx(go);
		    if (!ggo) continue;
		  
		    FIND (clicked_objs, j, ggo);
		    if (j == clicked_objs.end()) {
		  
		      // add object to selection
		      clicked_objs.insert(ggo);
		      if (ggo->gfx_obj) ggo->gfx_obj->selected = true;
		  
		    } else {
		      // remove object from selection
		      if (ggo->gfx_obj) ggo->gfx_obj->selected = false;
		      clicked_objs.erase(j);
		    }
		  }

		  // Add every other object near by to the selected set
		  FORALL (ids, i) {

		    GfxObject *go = GfxObject::id2obj((*i).id >> GfxObject::SUB_ID_LOG);
		    if (go == 0) { REM("foo"); continue; }
		    if (dynamic_cast<GfxTerrainTile*>(go)) continue;

		    GfxGameObject *ggo = GfxGameObject::get_gfx(go);
		    if (!ggo) continue;

		    FIND (sel_objs, j, ggo);
		    if (j == sel_objs.end()) {

		      // add object to selection

		      sel_objs.insert(ggo);
		      if (ggo->gfx_obj) ggo->gfx_obj->selected = true;

		    } else {
		      // remove object from selection
		      if (ggo->gfx_obj) ggo->gfx_obj->selected = false;
		      sel_objs.erase(j);
		    }
		  }

		/////////////////////////////////
		// Delete non-like units and static buildings 
		// from the selection
		// TODO - Another option to select multiple like bulidings -might- be nice.
		if (sel_objs.size() > 1) {

		    Vector<GfxGameObject*> del;

		    sint4 me = game->get_client_player();
		    FORALL (sel_objs, i){
		    	// Remove other players' objects and scenery
				if (*(*i)->gob->sod.owner != me){
					del.push_back(*i);
					continue;
				}
				
				// This shouldn't happen but just in case...
				if(clicked_objs.empty()){
					del.push_back(*i);
					continue;
				}

				// We can be fairly certain that units different in all of 
				// these ways are not the same type. 
				// Note: Please update if there is a more elegant method
				if (*(*i)->gob->sod.max_speed != *(*clicked_objs.rbegin())->gob->sod.max_speed) del.push_back(*i);
				else if (*(*i)->gob->sod.radius != *(*clicked_objs.rbegin())->gob->sod.radius) del.push_back(*i);
				else if (*(*i)->gob->sod.shape != *(*clicked_objs.rbegin())->gob->sod.shape) del.push_back(*i);
				else if (*(*i)->gob->sod.radius != *(*clicked_objs.rbegin())->gob->sod.radius) del.push_back(*i);
				else if (*(*i)->gob->sod.max_speed == 0) del.push_back(*i);
			}
		    FORALL (del, i) { (*i)->gfx_obj->selected = false; sel_objs.erase(*i); }
		    update_selected();
		  }
		}
	}
	return;
  }

  // clicks on minimap

  if (inside(last_mouse_x, last_mouse_y, mm_rect)) {

    if (button == GLUT_LEFT_BUTTON) {

      // position active view

      real4 x, y;

      mm_to_playfield(last_mouse_x, last_mouse_y, x, y);
      views[active_view].x_off = -x;
      views[active_view].z_off = -y;

      cur_click_state = JUST_CLICKED;
      return;

    } else if (button == GLUT_RIGHT_BUTTON) {

      // send objects there

      if (sel_objs.size() > 0) {
        real4 pf_x, pf_y;
        mm_to_playfield(last_mouse_x, last_mouse_y, pf_x, pf_y);
        real4 off_x, off_y;
        if (game->is_rotated()) {

          off_x = off_y = (game->get_map().get_width()+game->get_map().get_height())*0.25;

        } else if (!opt.minimap_rot) {

          off_x = game->get_map().get_width()*0.5;
          off_y = game->get_map().get_height()*0.5;

        } else {
          off_x = game->get_map().get_width()*0.5;
          off_y = game->get_map().get_height()*0.5;
        }
        move_objects(pf_x+off_x, pf_y+off_y);
        return;
      }
    }
  }
  
  cur_click_state = NORMAL;

  if (!inside(mx, my, views_rect)) return;

  if (button == GLUT_RIGHT_BUTTON ) {

    // generate move actions for selected objects

    // compute target location from mouse coordinates

    sint4 w = 2;
    SDL_Rect r = { mx - w/2, my-w/2, w, w };
    Vec3<real4> inter;
    Vector<IdZ> ids;

    cout << "SEND?" << endl;

    if (sel_objs.size() > 0 && pick(views[active_view], r, inter, ids, false) > 0) {
      move_objects(inter.x, inter.z);
    }
  }

  if (button == WHEEL_UP)   views[active_view].zoom(-ZOOM_OFF);
  if (button == WHEEL_DOWN) views[active_view].zoom(+ZOOM_OFF);

  update_active_view();
  last_mouse_x = mx;
  last_mouse_y = my;
}

//===================================================================

void GfxModule::event_mouse_motion(int mx, int my)
{
  Game::pick_game(game);

  my = opt.win_h-1-my;

  int button=0;
  if (left_mouse_down)   { button = GLUT_LEFT_BUTTON;   }
  if (middle_mouse_down) { button = GLUT_MIDDLE_BUTTON; }
  if (right_mouse_down)  { button = GLUT_RIGHT_BUTTON;  }
  
  ///////////////////////////////////////////
  // call the GUI mouse_motion event handler
  ObjAction *c = gui_obj ? gui_obj->action("event_mouse_motion") : 0;
  if (c) {
    SActParam p;
    p.obj.push_back(gui_obj);
    p.val.push_back(stype(mx));
    p.val.push_back(stype(my));
    p.val.push_back(stype(button));
    
    stype result = c->Act(p);
    if (!!result) return;
  }
  ///////////////////////////////////////////

  // motion on minimap
  if (inside(last_mouse_x, last_mouse_y, mm_rect) && !net_active) {

    if (left_mouse_down) {
      real4 x, y;
      //      mm_to_playfield(last_mouse_x, last_mouse_y, x, y);
      mm_to_playfield(mx, my, x, y);
      views[active_view].x_off = -x;
      views[active_view].z_off = -y;
      return;
    }
  }
  
  real4 dx = mx - sint4(last_mouse_x);
  real4 dy = my - sint4(last_mouse_y);

#if TRANSFORM_MOUSE_Y
  dy = -dy;
#endif

  if (middle_mouse_down) { // middle mouse button

    if (ctrl_down) {
      views[active_view].rotate_y(dx * 0.3);
      views[active_view].rotate_x(dy * 0.3);
    } else if (shift_down) {
      views[active_view].zoom(dy * 0.01);
    } else {

      real4 gx1, gx2, gy1, gy2;

      //      GfxView &v = views[active_view];
      const SDL_Rect &r = views[active_view].viewport;

      if (ground_intersection(r, last_mouse_x, last_mouse_y, gx1, gy1) &&
          ground_intersection(r, mx, my, gx2, gy2)) {
        views[active_view].x_off += gx2 - gx1;
        views[active_view].z_off += gy2 - gy1;
      }

    }

  } else if (left_mouse_down) { // left mouse button

    if (cur_click_state == NORMAL)
      net_active = true;

  } else {

    update_active_view();

  }

  last_mouse_x = mx;
  last_mouse_y = my;
}


void GfxModule::set_mods()
{
  Game::pick_game(game);

  int mod = glutGetModifiers();

  ObjAction *c = gui_obj ? gui_obj->action("set_mods") : 0;
  if (c) {
    SActParam p;
    p.obj.push_back(gui_obj);
    p.val.push_back(stype(mod));

    c->Act(p);
  }

  ctrl_down  = mod & GLUT_ACTIVE_CTRL;
  shift_down = mod & GLUT_ACTIVE_SHIFT;
  alt_down   = mod & GLUT_ACTIVE_ALT;
}

